package cn.harry.modular.system.service.impl;

import cn.harry.common.constant.CacheConstants;
import cn.harry.common.exception.ApiException;
import cn.harry.component.security.constant.SecurityConstants;
import cn.harry.component.security.model.SysUserDetails;
import cn.harry.component.security.utils.JwtUtils;
import cn.harry.component.security.utils.SecurityUtils;
import cn.harry.config.property.SecurityProperties;
import cn.harry.modular.auth.param.SysRegisterParam;
import cn.harry.modular.auth.vo.LoginResult;
import cn.harry.modular.auth.vo.SysUserInfo;
import cn.harry.modular.auth.vo.UserInfoResult;
import cn.harry.modular.mail.service.MailService;
import cn.harry.modular.sms.config.property.AliyunSmsProperties;
import cn.harry.modular.sms.service.SmsService;
import cn.harry.modular.system.domain.SysUser;
import cn.harry.modular.system.enums.ContactType;
import cn.harry.modular.system.mapper.SysUserMapper;
import cn.harry.modular.system.param.EmailBindingForm;
import cn.harry.modular.system.param.PhoneBindingForm;
import cn.harry.modular.system.service.SysUserRoleService;
import cn.harry.modular.system.service.SysUserService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author harry
 * @公众号 Harry技术
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    private final SecurityProperties securityProperties;
    private final AuthenticationManager authenticationManager;
    private final SysUserRoleService sysUserRoleService;
    private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder();
    private final RedisTemplate<String, Object> redisTemplate;

    private final MailService mailService;
    private final AliyunSmsProperties aliyunSmsProperties;
    private final SmsService smsService;

    @Override
    public LoginResult login(String username, String password) {

        LoginResult res = new LoginResult();
        String accessToken;
        Long expiration = securityProperties.getJwt().getTtl();
        // 密码需要客户端加密后传递
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
            SecurityContextHolder.getContext().setAuthentication(authenticate);

            // 认证成功后生成JWT令牌
            accessToken = JwtUtils.createToken(authenticate);

            res.setToken(accessToken);
            res.setExpiration(expiration);
            res.setTokenType(StrUtil.trim(SecurityConstants.JWT_TOKEN_PREFIX));

            return res;
        } catch (Exception e) {
            log.error("登录异常:{}", e.getMessage());
            throw new ApiException(e.getMessage());
        }
    }

    @Override
    public UserInfoResult getInfo() {
        SysUserDetails sysUserDetails = SecurityUtils.getSysUserDetails();
        SysUser user = sysUserDetails.getSysUser();
        UserInfoResult result = new UserInfoResult();
        result.setUsername(user.getUsername());
        result.setPermissions(sysUserDetails.getPermissions());
        result.setRoles(sysUserDetails.getRoles());
        result.setUserInfo(BeanUtil.copyProperties(user, SysUserInfo.class));
        return result;
    }

    @Override
    public IPage<SysUser> getPage(Page<SysUser> page, SysUser sysUser) {
        LambdaQueryWrapper<SysUser> wrapper = createWrapper(sysUser);
        List<SysUser> records = this.baseMapper.selectList(page, wrapper);
        page.setRecords(records);
        return page;
    }

    @Override
    public SysUser getUserById(Long userId) {
        SysUser user = getById(userId);
        if (userId != null) {
            // 获取用户所属的角色列表
            List<Long> roleIdList = sysUserRoleService.listRoleIdByUserId(userId);
            user.setRoleIds(roleIdList);
        }
        return user;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteByIds(Long[] ids) {
        for (Long id : ids) {
            removeCache(id);
            // 删除用户与角色关系
            sysUserRoleService.rebuildRole(id, null);
        }
        return removeByIds(Arrays.asList(ids));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#user.username")
    public boolean updateUser(SysUser user) {
        if (getDbUser(user)) {
            return false;
        }

        // 先删除用户与角色关系
        sysUserRoleService.rebuildRole(user.getId(), user.getRoleIds());
        // 更新用户
        return updateById(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean createUser(SysUser user) {
        if (getDbUser(user)) {
            return false;
        }
        boolean result = save(user);
        // 先删除用户与角色关系
        sysUserRoleService.rebuildRole(user.getId(), user.getRoleIds());
        return result;
    }

    @Override
    public boolean updatePasswordByUserId(Long userId, String password) {
        removeCache(userId);
        // 新密码
        String newPass = ENCODER.encode(password);
        return update(Wrappers.<SysUser>lambdaUpdate()
                .set(SysUser::getPassword, newPass)
                .eq(SysUser::getId, userId));
    }

    @Override
    public boolean bindEmail(EmailBindingForm param) {
        String verificationCode = param.getCode();
        String contact = param.getEmail();
        String verificationCodeKey = CacheConstants.EMAIL_CODE + contact;

        Object code = redisTemplate.opsForValue().get(verificationCodeKey);
        if (code == null) {
            throw new ApiException("邮箱验证码已失效");
        }
        if (!Objects.equals(code, verificationCode)) {
            throw new ApiException("邮箱验证码错误");
        }
        // 删除验证码
        redisTemplate.delete(verificationCodeKey);

        // 更新邮箱
        return update(Wrappers.<SysUser>lambdaUpdate()
                .set(SysUser::getEmail, contact)
                .eq(SysUser::getId, SecurityUtils.getUserId()));

    }

    @Override
    public boolean bindPhone(PhoneBindingForm param) {

        // 校验验证码
        String verificationCode = param.getCode();
        String contact = param.getPhone();
        String verificationCodeKey = CacheConstants.SMS_CODE + contact;

        Object code = redisTemplate.opsForValue().get(verificationCodeKey);
        if (code == null) {
            throw new ApiException("短信验证码已失效");
        }
        if (!Objects.equals(code, verificationCode)) {
            throw new ApiException("短信验证码错误");
        }
        // 删除验证码
        redisTemplate.delete(verificationCodeKey);

        // 更新手机号
        return update(Wrappers.<SysUser>lambdaUpdate()
                .set(SysUser::getPhone, contact)
                .eq(SysUser::getId, SecurityUtils.getUserId()));

    }

    @Override
    public boolean sendVerificationCode(String contact, ContactType type) {
        // 随机生成4位验证码
        String code = RandomUtil.randomNumbers(4);

        // 发送验证码
        String verificationCodePrefix = null;
        switch (type) {
            case MOBILE:
                // 获取修改密码的模板code
                String changePasswordSmsTemplateCode = aliyunSmsProperties.getTemplateCodes().get("changePassword");
                smsService.sendSmsCode(contact, changePasswordSmsTemplateCode, code);
                verificationCodePrefix = CacheConstants.SMS_CODE;
                break;
            case EMAIL:
                mailService.sendMail(contact, "验证码", "您的验证码是：" + code);
                verificationCodePrefix = CacheConstants.EMAIL_CODE;
                break;
            default:
                throw new ApiException("不支持的联系方式类型");
        }
        // 存入 redis 用于校验, 5分钟有效
        redisTemplate.opsForValue().set(verificationCodePrefix + contact, code, 5, TimeUnit.MINUTES);
        return true;
    }

    @Override
    public boolean register(SysRegisterParam registerParam) {

        SysUser user = BeanUtil.copyProperties(registerParam, SysUser.class);
        if (getDbUser(user)) {
            return false;
        }

        List<Long> roleIds = new ArrayList<>();
        switch (registerParam.getRole()) {
            case "1":
                roleIds.add(1L);
                break;
            case "2":
                roleIds.add(2L);
                break;
            default:
                throw new ApiException("不支持的角色类型");
        }

        user.setRoleIds(roleIds);
        user.setDeptId(1L);
        user.setPassword(user.getPassword());
        user.setDeptName("Harry技术");
        boolean result = save(user);
        // 先删除用户与角色关系
        sysUserRoleService.rebuildRole(user.getId(), user.getRoleIds());
        return result;

    }


    private LambdaQueryWrapper<SysUser> createWrapper(SysUser user) {
        LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StrUtil.isNotEmpty(user.getUsername()), SysUser::getUsername, user.getUsername());
        wrapper.like(StrUtil.isNotEmpty(user.getPhone()), SysUser::getPhone, user.getPhone());
        wrapper.eq(StrUtil.isNotEmpty(user.getStatus()), SysUser::getStatus, user.getStatus());
        wrapper.eq(ObjectUtil.isNotNull(user.getDeptId()), SysUser::getDeptId, user.getDeptId());
        return wrapper;
    }

    private boolean getDbUser(SysUser user) {
        String password = user.getPassword();
        SysUser dbUser = this.baseMapper.selectByUsername(user.getUsername());
        if (dbUser != null && !Objects.equals(dbUser.getId(), user.getId())) {
            return true;
        }
        if (StrUtil.isBlank(password)) {
            user.setPassword(null);
        } else {
            user.setPassword(ENCODER.encode(user.getPassword()));
        }
        return false;
    }

    /**
     * 删除缓存
     *
     * @param userId 用户id
     */
    private void removeCache(Long userId) {
        SysUser user = getById(userId);
        if (user != null) {
            redisTemplate.delete(CacheConstants.USER_DETAILS + ":" + user.getUsername());
        }
    }
}




